package main

import (
	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"image/color"
	"log"
	"math/rand"
	"strconv"
)

const (
	WindowWidth  = 800
	WindowHeight = 800
	BlockSize    = 20
	XRange       = 20
	YRange       = 30
)

var (
	numImage               [10]*ebiten.Image
	StartButton, FailImage *ebiten.Image
	UnitColorI             = color.RGBA{255, 0, 0, 255}
	UnitColorJ             = color.RGBA{0, 255, 0, 255}
	UnitColorL             = color.RGBA{0, 0, 255, 255}
	UnitColorS             = color.RGBA{10, 10, 0, 255}
	UnitColorZ             = color.RGBA{10, 0, 10, 255}
	UnitColorT             = color.RGBA{0, 10, 10, 255}
	UnitColorO             = color.RGBA{10, 10, 10, 255}
	UnitTypes              = map[int]map[int][]*Blocks{
		// I
		0: map[int][]*Blocks{
			// up
			0: {
				{1, 0, UnitColorI}, {1, 1, UnitColorI}, {1, 2, UnitColorI}, {1, 3, UnitColorI},
			},
			// right
			1: {
				{0, 1, UnitColorI}, {1, 1, UnitColorI}, {2, 1, UnitColorI}, {3, 1, UnitColorI},
			},
			// down
			2: {
				{1, 0, UnitColorI}, {1, 1, UnitColorI}, {1, 2, UnitColorI}, {1, 3, UnitColorI},
			},
			// left
			3: {
				{0, 1, UnitColorI}, {1, 1, UnitColorI}, {2, 1, UnitColorI}, {3, 1, UnitColorI},
			},
		},
		//  J
		1: map[int][]*Blocks{
			// up
			0: {
				{1, 0, UnitColorJ}, {1, 1, UnitColorJ}, {1, 2, UnitColorJ}, {0, 2, UnitColorJ},
			},
			// right
			1: {
				{0, 1, UnitColorJ}, {1, 1, UnitColorJ}, {2, 1, UnitColorJ}, {0, 0, UnitColorJ},
			},
			// down
			2: {
				{1, 0, UnitColorJ}, {1, 1, UnitColorJ}, {1, 2, UnitColorJ}, {2, 0, UnitColorJ},
			},
			// left
			3: {
				{0, 0, UnitColorJ}, {1, 0, UnitColorJ}, {2, 0, UnitColorJ}, {2, 1, UnitColorJ},
			},
		},
		// L
		2: map[int][]*Blocks{
			// up
			0: {
				{1, 0, UnitColorL}, {1, 1, UnitColorL}, {1, 2, UnitColorL}, {2, 2, UnitColorL},
			},
			// right
			1: {
				{0, 0, UnitColorL}, {1, 0, UnitColorL}, {2, 0, UnitColorL}, {0, 1, UnitColorL},
			},
			// down
			2: {
				{0, 0, UnitColorL}, {1, 0, UnitColorL}, {1, 1, UnitColorL}, {1, 2, UnitColorL},
			},
			// left
			3: {
				{0, 1, UnitColorL}, {1, 1, UnitColorL}, {2, 1, UnitColorL}, {2, 0, UnitColorL},
			},
		},
		// S
		3: map[int][]*Blocks{
			// up
			0: {
				{0, 1, UnitColorS}, {1, 1, UnitColorS}, {1, 0, UnitColorS}, {2, 0, UnitColorS},
			},
			// right
			1: {
				{0, 0, UnitColorS}, {0, 1, UnitColorS}, {1, 1, UnitColorS}, {1, 2, UnitColorS},
			},
			// down
			2: {
				{0, 1, UnitColorS}, {1, 1, UnitColorS}, {1, 0, UnitColorS}, {2, 0, UnitColorS},
			},
			// left
			3: {
				{0, 0, UnitColorS}, {0, 1, UnitColorS}, {1, 1, UnitColorS}, {1, 2, UnitColorS},
			},
		},
		// Z
		4: map[int][]*Blocks{
			// up
			0: {
				{0, 0, UnitColorZ}, {1, 0, UnitColorZ}, {1, 1, UnitColorZ}, {2, 1, UnitColorZ},
			},
			// right
			1: {
				{1, 0, UnitColorZ}, {1, 1, UnitColorZ}, {0, 1, UnitColorZ}, {0, 2, UnitColorZ},
			},
			// down
			2: {
				{0, 0, UnitColorZ}, {1, 0, UnitColorZ}, {1, 1, UnitColorZ}, {2, 1, UnitColorZ},
			},
			// left
			3: {
				{1, 0, UnitColorZ}, {1, 1, UnitColorZ}, {0, 1, UnitColorZ}, {0, 2, UnitColorZ},
			},
		},
		// T
		5: map[int][]*Blocks{
			// up
			0: {
				{1, 0, UnitColorT}, {1, 1, UnitColorT}, {0, 1, UnitColorT}, {2, 1, UnitColorT},
			},
			// right
			1: {
				{1, 0, UnitColorT}, {1, 1, UnitColorT}, {1, 2, UnitColorT}, {2, 1, UnitColorT},
			},
			// down
			2: {
				{0, 0, UnitColorT}, {1, 0, UnitColorT}, {2, 0, UnitColorT}, {1, 1, UnitColorT},
			},
			// left
			3: {
				{0, 1, UnitColorT}, {1, 0, UnitColorT}, {1, 1, UnitColorT}, {1, 2, UnitColorT},
			},
		},
		// O
		6: map[int][]*Blocks{
			// up
			0: {
				{0, 0, UnitColorO}, {1, 1, UnitColorO}, {1, 0, UnitColorO}, {0, 1, UnitColorO},
			},
			// right
			1: {
				{0, 0, UnitColorO}, {1, 1, UnitColorO}, {1, 0, UnitColorO}, {0, 1, UnitColorO},
			},
			// down
			2: {
				{0, 0, UnitColorO}, {1, 1, UnitColorO}, {1, 0, UnitColorO}, {0, 1, UnitColorO},
			},
			// left
			3: {
				{0, 0, UnitColorO}, {1, 1, UnitColorO}, {1, 0, UnitColorO}, {0, 1, UnitColorO},
			},
		},
	}
)

func init() {
	startButton, _, err := ebitenutil.NewImageFromFile("start.png")
	if err != nil {
		panic(err)
	}
	StartButton = startButton
	FailImage, _, err = ebitenutil.NewImageFromFile("fail.png")
	if err != nil {
		panic(err)
	}
	FailImage, _, err = ebitenutil.NewImageFromFile("fail.png")
	if err != nil {
		panic(err)
	}
	for i := 0; i < 10; i++ {
		image, _, err := ebitenutil.NewImageFromFile("numbers_image_" + strconv.Itoa(i) + ".png")
		numImage[i] = image
		if err != nil {
			panic(err)
		}
	}
}

type Tetris struct {
	start         bool
	fail          bool
	count         int
	current, next *Unit
	blocks        map[int]map[int]*Blocks
	score         int
}

func NewRandomUnit() *Unit {
	var u = &Unit{
		unitType:  rand.Intn(7),
		direction: rand.Intn(4),
	}
	u.blocks = UnitTypes[u.unitType][u.direction]
	return u
}

type Unit struct {
	unitType  int //I J L S Z T O
	blocks    []*Blocks
	direction int
	X, Y      int
}

type Blocks struct {
	X, Y  int
	Color color.Color
}

func (u *Unit) setXY(x, y int) {
	u.X = x
	u.Y = y
}

func (g *Tetris) Update() error {
	if !g.start {
		if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
			if mx, my := ebiten.CursorPosition(); mx >= 600 && mx <= 690 && my >= 600 && my <= 645 {
				g.start = true
				g.score = 0
				g.current = NewRandomUnit()
				g.current.X = XRange / 2
				g.current.Y = -4
				g.next = NewRandomUnit()
				g.blocks = make(map[int]map[int]*Blocks)
				for y := 0; y < YRange; y++ {
					g.blocks[y] = make(map[int]*Blocks)
				}
			}
		}
	}
	if g.start {
		g.count++
		if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) {
			g.ToLeft()
		}
		if ebiten.IsKeyPressed(ebiten.KeyArrowRight) {
			g.ToRight()
		}
		if ebiten.IsKeyPressed(ebiten.KeyArrowDown) {
			g.ToBottom()
		}
		if ebiten.IsKeyPressed(ebiten.KeyArrowUp) {
			g.Transform()
		}
		if g.count%2 == 0 {
			var ok = g.ToBottom()
			if !ok {
				if g.current.Y <= 0 {
					g.start = false
					g.fail = true
					g.current = nil
					g.next = nil
					return nil
				}
				for i := range g.current.blocks {
					g.blocks[g.current.Y+g.current.blocks[i].Y][g.current.X+g.current.blocks[i].X] = g.current.blocks[i]
				}
				for y := YRange - 1; y > -4; {
					var isFull = true
					for x := 0; x < XRange; x++ {
						if g.blocks[y][x] == nil {
							isFull = false
							break
						}
					}
					if !isFull {
						y--
						continue
					}
					g.score += 100
					for y2 := y; y2 > -4; y2-- {
						g.blocks[y2] = g.blocks[y2-1]
					}
					g.blocks[-4] = make(map[int]*Blocks)
				}
				g.current = g.next
				g.current.X = XRange / 2
				g.current.Y = -4
				g.next = NewRandomUnit()
			}
		}
	}
	return nil
}

func (g *Tetris) Transform() bool {
	var success = true
	g.current.direction = (g.current.direction + 1) % 4
	g.current.blocks = UnitTypes[g.current.unitType][g.current.direction]
	for i := range g.current.blocks {
		// 触底
		if g.current.blocks[i].X+g.current.X >= XRange ||
			g.current.blocks[i].Y+g.current.Y >= YRange ||
			g.current.blocks[i].X+g.current.X < 0 {
			success = false
			break
		}
		// 碰撞
		if g.blocks[g.current.Y+g.current.blocks[i].Y][g.current.X+g.current.blocks[i].X] != nil {
			success = false
			break
		}
	}
	if !success {
		g.current.direction = (g.current.direction - 1) % 4
	}
	return success
}

func (g *Tetris) ToRight() bool {
	var success = true
	g.current.X++
	for i := range g.current.blocks {
		// 触底
		if g.current.blocks[i].X+g.current.X >= XRange {
			success = false
			break
		}
		// 碰撞
		if g.blocks[g.current.Y+g.current.blocks[i].Y][g.current.X+g.current.blocks[i].X] != nil {
			success = false
			break
		}
	}
	if !success {
		g.current.X--
	}
	return success
}

func (g *Tetris) ToLeft() bool {
	var success = true
	g.current.X--
	for i := range g.current.blocks {
		// 触底
		if g.current.blocks[i].X+g.current.X < 0 {
			success = false
			break
		}
		// 碰撞
		if g.blocks[g.current.Y+g.current.blocks[i].Y][g.current.X+g.current.blocks[i].X] != nil {
			success = false
			break
		}
	}
	if !success {
		g.current.X++
	}
	return success
}

func (g *Tetris) ToBottom() bool {
	var success = true
	g.current.Y++
	for i := range g.current.blocks {
		// 触底
		if g.current.blocks[i].Y+g.current.Y >= YRange {
			success = false
			break
		}
		// 碰撞
		if g.blocks[g.current.Y+g.current.blocks[i].Y][g.current.X+g.current.blocks[i].X] != nil {
			success = false
			break
		}
	}
	if !success {
		g.current.Y--
	}
	return success
}

func (g *Tetris) Draw(screen *ebiten.Image) {
	// 绘制背景颜色
	screen.Fill(color.RGBA{R: 0xb0, G: 0xc4, B: 0xde, A: 0xff})
	var mainArea = ebiten.NewImage(XRange*BlockSize, YRange*BlockSize)
	mainArea.Fill(color.White)
	var op = &ebiten.DrawImageOptions{}
	if g.start {
		for i := range g.current.blocks {
			var block = ebiten.NewImage(0.9*BlockSize, 0.9*BlockSize)
			block.Fill(g.current.blocks[i].Color)
			op.GeoM.Reset()
			op.GeoM.Translate(float64(g.current.blocks[i].X+g.current.X)*BlockSize+0.05*BlockSize,
				float64(g.current.blocks[i].Y+g.current.Y)*BlockSize+0.05*BlockSize)
			mainArea.DrawImage(block, op)
		}
		for y := 0; y < YRange; y++ {
			for x := 0; x < XRange; x++ {
				if g.blocks[y][x] != nil {
					var block = ebiten.NewImage(0.9*BlockSize, 0.9*BlockSize)
					block.Fill(g.blocks[y][x].Color)
					op.GeoM.Reset()
					op.GeoM.Translate(float64(x)*BlockSize+0.05*BlockSize,
						float64(y)*BlockSize+0.05*BlockSize)
					mainArea.DrawImage(block, op)
				}
			}
		}
	}
	if g.fail {
		op.GeoM.Reset()
		op.GeoM.Scale(0.2, 0.2)
		op.GeoM.Translate(150, 200)
		mainArea.DrawImage(FailImage, op)
	}
	op.GeoM.Reset()
	op.GeoM.Translate(150, 100)
	screen.DrawImage(mainArea, op)

	// draw next Block
	var nextTipArea = ebiten.NewImage(4*BlockSize, 4*BlockSize)
	nextTipArea.Fill(color.White)
	if g.next != nil {
		for i := range g.next.blocks {
			var block = ebiten.NewImage(0.9*BlockSize, 0.9*BlockSize)
			block.Fill(g.next.blocks[i].Color)
			op.GeoM.Reset()
			op.GeoM.Translate(float64(g.next.blocks[i].X)*BlockSize+0.05*BlockSize,
				float64(g.next.blocks[i].Y)*BlockSize+0.05*BlockSize)
			nextTipArea.DrawImage(block, op)
		}
	}

	op.GeoM.Reset()
	op.GeoM.Translate(600, 100)
	screen.DrawImage(nextTipArea, op)
	if !g.start {
		op.GeoM.Reset()
		op.GeoM.Scale(0.1, 0.1)
		op.GeoM.Translate(600, 600)
		screen.DrawImage(StartButton, op)
	}

	var score = strconv.Itoa(g.score)
	for i := range score {
		op.GeoM.Reset()
		op.GeoM.Scale(1, 1)
		op.GeoM.Translate(600+float64(i)*30, 400)
		num, _ := strconv.Atoi(string(score[i]))
		screen.DrawImage(numImage[num], op)
	}
}

func (g *Tetris) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return WindowWidth, WindowHeight
}

func main() {
	ebiten.SetWindowSize(WindowWidth, WindowHeight)
	ebiten.SetWindowTitle("俄罗斯方块")
	ebiten.SetTPS(10)
	if err := ebiten.RunGame(&Tetris{}); err != nil {
		log.Fatal(err)
	}
}
